package cn.yangliu.nacos.comm.oauth;

import java.util.Objects;

import cn.yangliu.nacos.comm.JsonResult;
import cn.yangliu.nacos.comm.config.Oauth2Properties;
import cn.yangliu.nacos.comm.constant.SecurityConstants;
import cn.yangliu.nacos.comm.model.Oauth2InsertModel;
import cn.yangliu.nacos.comm.model.Oauth2Token;
import cn.yangliu.nacos.comm.model.Oauth2UpdateOtherModel;
import cn.yangliu.nacos.comm.model.Oauth2UpdateOtherPasswordModel;
import cn.yangliu.nacos.comm.model.Oauth2UpdatePasswordModel;
import cn.yangliu.nacos.comm.model.Oauth2UpdateSelfModel;
import cn.yangliu.nacos.comm.model.Oauth2UserModel;
import cn.yangliu.nacos.comm.model.Oauth2UserQueryModel;
import cn.yangliu.nacos.comm.tools.ResponseUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;

/**
 * The type of nacos.
 *
 * @author 问道于盲
 * @date 2019 -12-31
 */
@Service
@Slf4j
public class Oauth2Service {

    /**
     * current class instance's member.
     * The Oauth 2 feign client.
     */
    @Autowired
    private Oauth2FeignClient oauth2FeignClient;

    /**
     * current class instance's member.
     * The Oauth 2 user service.
     */
    @Autowired(required = false)
    private Oauth2UserStorege oauth2UserStorege;

    /**
     * current class instance's member.
     * The Properties.
     */
    @Autowired
    private Oauth2Properties properties;

    /**
     * current class instance's member.
     * The Token handler.
     */
    @Autowired(required = false)
    private TokenHandler tokenHandler;

    /**
     * Login oauth 2 token.
     *
     * @param username the username
     * @param password the password
     * @return the oauth 2 token
     */
    @Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 1.5))
    public Oauth2Token login(String username, String password) {
        JsonResult<Oauth2Token> result = oauth2FeignClient.token(username, password,
                SecurityConstants.AUTHORIZED_GRANT_TYPES_PASSWORD,
                null, null, null, null);
        Oauth2Token oauth2Token = ResponseUtils.checkResult(result);
        storeToken(username, oauth2Token);
        return oauth2Token;
    }

    /**
     * Store token.
     *
     * @param username    the username
     * @param oauth2Token the oauth 2 token
     */
    private void storeToken(String username, Oauth2Token oauth2Token) {
        if (Objects.isNull(tokenHandler)) {
            return;
        }
        tokenHandler.store(username, oauth2Token);
    }

    /**
     * Login success.
     *
     * @param oauth2UserModel the oauth 2 user
     */
    private void saveUser(Oauth2UserModel oauth2UserModel) {
        if (Objects.isNull(oauth2UserModel)) {
            return;
        }

        oauth2UserStorege.store(oauth2UserModel);
    }

    /**
     * Login by client oauth 2 token.
     *
     * @return the oauth 2 token
     */
    @Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 1.5))
    public Oauth2Token loginByClient() {
        JsonResult<Oauth2Token> result = oauth2FeignClient.token(null, null,
                SecurityConstants.AUTHORIZED_GRANT_TYPES_CLIENT_CREDENTIALS,
                properties.getClientId(), properties.getClientSecret(), null, null);
        return ResponseUtils.checkResult(result);
    }

    /**
     * Login by code oauth 2 token.
     *
     * @param code        the code
     * @param redirectUri the redirect uri
     * @return the oauth 2 token
     */
    @Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 1.5))
    public Oauth2Token loginByCode(String code, String redirectUri) {
        JsonResult<Oauth2Token> result = oauth2FeignClient.token(null, null,
                SecurityConstants.AUTHORIZED_GRANT_TYPES_AUTHORIZATION_CODE,
                properties.getClientId(), properties.getClientSecret(), code, redirectUri);
        return ResponseUtils.checkResult(result);
    }

    /**
     * Insert oauth 2 user.
     *
     * @param model the model
     * @param token the token
     * @return the oauth 2 user
     */
    @Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 1.5))
    public Oauth2UserModel insert(Oauth2InsertModel model, String token) {
        token = SecurityConstants.AUTHORIZATION_BEARER + token;
        JsonResult<Oauth2UserModel> result = oauth2FeignClient.insert(model, token);
        Oauth2UserModel oauth2UserModel = ResponseUtils.checkResult(result);
        saveUser(oauth2UserModel);
        return oauth2UserModel;
    }

    /**
     * Update self oauth 2 user.
     *
     * @param model the model
     * @param token the token
     * @return the oauth 2 user
     */
    @Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 1.5))
    public Oauth2UserModel updateSelf(Oauth2UpdateSelfModel model, String token) {
        token = SecurityConstants.AUTHORIZATION_BEARER + token;
        JsonResult<Oauth2UserModel> result = oauth2FeignClient.updateSelf(model, token);
        return ResponseUtils.checkResult(result);
    }

    /**
     * Update other oauth 2 user.
     *
     * @param model the model
     * @param token the token
     * @return the oauth 2 user
     */
    @Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 1.5))
    public Oauth2UserModel updateOther(Oauth2UpdateOtherModel model, String token) {
        token = SecurityConstants.AUTHORIZATION_BEARER + token;
        JsonResult<Oauth2UserModel> result = oauth2FeignClient.updateOther(model, token);
        return ResponseUtils.checkResult(result);
    }

    /**
     * Update self password boolean.
     *
     * @param password the password
     * @param token    the token
     * @return the boolean
     */
    @Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 1.5))
    public Boolean updateSelfPassword(String password, String token) {
        token = SecurityConstants.AUTHORIZATION_BEARER + token;
        Oauth2UpdatePasswordModel model = Oauth2UpdatePasswordModel.build(password);
        JsonResult<Boolean> result = oauth2FeignClient.updateSelfPassword(model, token);
        return ResponseUtils.checkResult(result);
    }

    /**
     * Update other password boolean.
     *
     * @param model the model
     * @param token the token
     * @return the boolean
     */
    @Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 1.5))
    public Boolean updateOtherPassword(Oauth2UpdateOtherPasswordModel model, String token) {
        token = SecurityConstants.AUTHORIZATION_BEARER + token;
        JsonResult<Boolean> result = oauth2FeignClient.updateOtherPassword(model, token);
        return ResponseUtils.checkResult(result);
    }

    /**
     * Principal oauth 2 user.
     *
     * @param token the token
     * @return the oauth 2 user
     */
    @Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 1.5))
    public Oauth2UserModel principal(String token) {
        token = SecurityConstants.AUTHORIZATION_BEARER + token;
        JsonResult<Oauth2UserModel> result = oauth2FeignClient.principal(token);
        Oauth2UserModel oauth2UserModel = ResponseUtils.checkResult(result);
        saveUser(oauth2UserModel);
        return oauth2UserModel;
    }

    /**
     * Delete boolean.
     *
     * @param id    the id
     * @param token the token
     * @return the boolean
     */
    @Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 1.5))
    public Boolean delete(Long id, String token) {
        token = SecurityConstants.AUTHORIZATION_BEARER + token;
        JsonResult<Boolean> result = oauth2FeignClient.delete(id, token);
        return ResponseUtils.checkResult(result);
    }

    /**
     * Find oauth 2 user.
     *
     * @param model the model
     * @param token the token
     * @return the oauth 2 user
     */
    @Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 1.5))
    public Oauth2UserModel findOne(Oauth2UserQueryModel model, String token) {
        token = SecurityConstants.AUTHORIZATION_BEARER + token;
        JsonResult<Oauth2UserModel> result = oauth2FeignClient.findOne(model, token);
        return ResponseUtils.checkResult(result);
    }

    /**
     * Check exist boolean.
     *
     * @param model the model
     * @param token the token
     * @return the boolean
     */
    @Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000L, multiplier = 1.5))
    public Boolean checkExist(Oauth2UserQueryModel model, String token) {
        token = SecurityConstants.AUTHORIZATION_BEARER + token;
        JsonResult<Boolean> result = oauth2FeignClient.checkExist(model, token);
        return ResponseUtils.checkResult(result);
    }

    @Recover
    public void recover(Exception e) {
        log.warn(e.getMessage());
    }
}
